package com.qf.listener;

import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

@Component
public class RabbitMQListener {

//    @RabbitListener(queues = "bootQue1")
    public void consumer1(Message msg){
        byte[] body = msg.getBody();
        System.out.println("body:" + new String(body));

        MessageProperties messageProperties = msg.getMessageProperties();
        System.out.println("messageProperties:" + messageProperties);
    }

//    @RabbitListener(queues = "bootQue2")
    public void consumer2(Message msg){
        byte[] body = msg.getBody();
        System.out.println("body2:" + new String(body));

        MessageProperties messageProperties = msg.getMessageProperties();
        System.out.println("messageProperties2:" + messageProperties);
    }

    /*
        如果消费者不做任何配置，默认springboot中是自动提交消息
            默认处理方式：
                如果代码出现异常，消息会重新入队，重新消费
                如果代码不出现异常，消息自动确认消费，消息就会从服务器删除

            实际开发时，我们的业务有时候成功或者失败，不是通过异常来标识的，会有一个标记返回
                比如 1 表示成功，  2 表示失败

               针对上述非异常标识成功或失败的问题，我们需要手动提交，不可以使用springboot的默认提交模式。
                  配置方式：
                    ① 开启手动提交（配置文件 或者 消费上单独配）
                    ② 消费者接收一个Channel对象，基于这个对象进行提交或者不提交

     */
    int num = 2;
//    @RabbitListener(queues = "durableQue"/*,ackMode = "auto"*/)
    public void consumer3(Message msg, Channel channel) throws IOException {
        MessageProperties messageProperties = msg.getMessageProperties();
        int i = mockService(num);
        if (i == 1){
            byte[] body = msg.getBody();
            System.out.println("body3:" + new String(body));


            System.out.println("messageProperties3:" + messageProperties);

            //确认消费
            channel.basicAck(messageProperties.getDeliveryTag(),false);
        }else if (i == 2){
            System.out.println("消息处理失败了,重试");

            //消费失败，不确认，重新入队
            channel.basicNack(messageProperties.getDeliveryTag(),false,true);
            num = 1;
        }

    }

    //模拟一个外部业务
    public int mockService(int num){
        return num;
    }


//    @RabbitListener(queues = "dead_common_que")
    public void consumer4(Message msg, Channel channel) throws IOException {
        System.out.println("收到消息：" + new String(msg.getBody()));
        //确认消费,不产生死信
        //channel.basicAck(msg.getMessageProperties().getDeliveryTag(),false);

        //不确认消息，并且消息不重新入队，产生死信
        channel.basicNack(msg.getMessageProperties().getDeliveryTag(),false,false);
    }

//    @RabbitListener(queues = "dead_que")
    public void consumer5(Message msg, Channel channel) throws IOException {
        System.out.println("检查订单状态，未支付订单，取消，并归还库存");

        //检查订单状态的时间
        System.out.println("检查订单状态的时间：" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));

        //确认消费,不产生死信
        channel.basicAck(msg.getMessageProperties().getDeliveryTag(),false);
    }


    /*
        幂等问题：
            查询操作 -- 幂等
            删除操作 -- 幂等
            修改操作
                设置操作 -- 幂等
                    update 表名 set 列名1=值1,列名2=值2,... where id = 1;
                计算操作 -- 非幂等
                    update 表名 set 列名1=列名1+值1,列名2=列名2+值2,... where id = 1;

                解决方案：
                    ① 基于redis存储唯一标记. setnx存，key使用消息的唯一标记
                    ② 状态字段
                        update 表名 set status='已修改',列名1=列名1+值1,列名2=列名2+值2,... where id = 1 and status='未修改';
            新增操作 -- 非幂等
                解决方案：
                    ① 基于redis存储唯一标记. setnx存，key使用消息的唯一标记
                    ② 先根据消息的唯一标记数据库查询，如果能查到，已经新增，不能再新增了，如果查不到，没有新增，需要执行新增操作
                        查询操作
                        新增操作

                        方式一：分布式锁 （redis、zookeeper）
                        方式二：利用数据库自己的锁
                            select * from 表名 where id=1 for update;  //查询数据时，加写锁

                            如果使用数据库自身的锁资源，要保障查询操作和新增操作必须在同一个事务中

                    ③ 利用数据库的唯一约束  唯一标识字段设置唯一约束（消费者要捕获唯一约束异常）


     */

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @RabbitListener(queues = "mideng_que")
    public void consumer6(Message msg, Channel channel) throws IOException {
        MessageProperties messageProperties = msg.getMessageProperties();
        String messageId = messageProperties.getMessageId();

        Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent(messageId, "0");

        if (aBoolean) {
            try {
                System.out.println(messageId);
                System.out.println(new String(msg.getBody()));
            }catch (Exception e){
                stringRedisTemplate.delete(messageId);
                channel.basicNack(msg.getMessageProperties().getDeliveryTag(),false,true);

                return;
            }
        }

        //确认消息
        channel.basicAck(msg.getMessageProperties().getDeliveryTag(),false);
    }


}
